/**
 *  Project Vorpal
 *  \file Assembler.h
 *  \author Russ Webb
 *
 *  \date 06/08/2008
 */

#ifndef VORPAL_ASSEMBLER_H
#define VORPAL_ASSEMBLER_H

#include "Common.h"
#include "Instructions.h"
#include "Messages.h"
#include <math.h>
#include <vector>
#include <map>
#include <iostream>
#include <string>

namespace Vorpal{

	/// Class that gathers instructions read by compiler and formats these to be interpreted by the virtual machine
	class CodeWriter{
	public:
		CodeWriter() : numParams(0), varParamIndex(0), numLocals(0), debugOffsetsRecorded(0){ }
		
		uint32_t Size() const{
			return kCodeFirstInstructionIndex + OpcodeSizeInCells() + operands.size() + debug.size();
		}
		
		void Get(Cell *code, uint32_t size){
			Assert(size >= Size());
			code[kCodeSizeIndex].as_uint = Size();
			code[kCodeNumParamIndex].as_uint = numParams;
			code[kCodeVarParamIndex].as_uint = varParamIndex;
			code[kCodeNumLocalIndex].as_uint = numLocals;
			code[kCodeDebugInfoIndex].as_uint = 0;

			code[kCodeFirstOperandIndexIndex].as_uint = kCodeFirstInstructionIndex + OpcodeSizeInCells();
			uint32_t here = kCodeFirstInstructionIndex;
			for(uint32_t i = 0; i < opcodes.size(); ){
				Cell cell;
				memset(&cell, 0, sizeof(cell));
				for(uint32_t j = 0; j < kCellSize && i < opcodes.size(); ++j){
					cell.as_bytes[j] = opcodes[i++];
				}
				code[here++] = cell;
			}
			Assert(here == code[kCodeFirstOperandIndexIndex].as_uint);
			loop(i, 0, operands.size()){
				code[here++] = operands[i];
			}
			if(debug.size() > 0){
				code[kCodeDebugInfoIndex].as_uint = kCodeFirstInstructionIndex + OpcodeSizeInCells() + operands.size();
				debug[debug[0].as_uint].as_uint = debugOffsetsRecorded;
				loop(i, 0, debug.size()){
					code[here++] = debug[i];
				}
			}
		}
		
		void SetDebugLineInfo(const char *filePath, uint currentLineNumber){
			if(debug.size() == 0){
				SetDebugFilename(filePath);
			}
			Cell lineInfo;
			lineInfo.as_uint = currentLineNumber;
			while(debugOffsetsRecorded < opcodes.size()){
				debug.push_back(lineInfo);
				debugOffsetsRecorded += 1;
			}
		}
		
	protected:
		void SetDebugFilename(const char *filePath){
			Assert(filePath);
			Assert(debug.size() == 0);
			AddData(debug, filePath, strlen(filePath) + 1, true /*prefixWithSize*/);
			Cell lengthOfLineInfo;
			memset(&lengthOfLineInfo, 0, sizeof(Cell));
			WriteCell(debug, lengthOfLineInfo);
		}
		
		void Debug(){
			loop(i, 0, opcodes.size()){
				printf("__ %s", instructions[int(opcodes[i])].name);
				loop(i, 0, instructions[int(opcodes[i])].smallOperands){
					printf("__ %i", int(opcodes[i]));
					i += 1;
				}
				puts("");
			}
		}
		
		void Reset(){
			labels.clear();
			jumpLocations.clear();
			numParams = 0;
			numLocals = 0;
			opcodes.resize(0);
			operands.resize(0);		
		}
		
		void WriteInstruction(int8_t opcode){
			opcodes.push_back(opcode);
		}
		
		void WriteOperand(Cell operand){
			WriteCell(operands, operand);			
		}
		
		void NumParams(uint32_t numParams, uint32_t varParamIndex){
			this->numParams = numParams;
			this->varParamIndex = varParamIndex;
		}
		
		void NumLocals(uint32_t numLocals){
			this->numLocals = numLocals;
		}
		
		void Label(const char *labelStr){
			if(labels.find(string(labelStr)) != labels.end()){
				// label already exists
				cout << "Duplicate Label: " << labelStr << endl;
				Failure(ERR_DUPLICATE_LABEL); 
			}
			labels[string(labelStr)] = Location(opcodes.size(), operands.size());			
		}
		
		void JumpPatchPoint(const char *jumpToStr){
			PatchPoint patch(string(jumpToStr), Location(opcodes.size(), operands.size()) );
			jumpLocations.push_back(patch);
		}
		
		void PatchJumps(){
			loop(i, 0, jumpLocations.size()){
				string label = jumpLocations[i].first;
				Location from = jumpLocations[i].second;
				if(labels.find(label) == labels.end()){
					// label does not exists
					cout << "Missing Label:" << label << endl;
					Failure(ERR_JUMPS_MISSING_LABEL); 				
				}
				Cell cell;
				cell.jump.ip = int(labels[label].first) - int(from.first);
				cell.jump.op = int(labels[label].second) - int(from.second);
				operands[from.second] = cell;
			}
			labels.clear();
			jumpLocations.clear();
		}		

		void AddOperandData(const char *mem, uint32_t len, bool prefixWithSize){
			AddData(operands, mem, len, prefixWithSize);
		}
		
		void WriteCell(vector< Cell > &dest, Cell value){
			dest.push_back(value);
		}
		
		void AddData(vector< Cell > &dest, const char *mem, uint32_t len, bool prefixWithSize){
			Cell cell;
			memset(&cell, 0, kCellSize);

			uint32_t sizeIndex = dest.size();
			if(prefixWithSize){
				WriteCell(dest, cell);
			}

			uint32_t cellIndex = 0;
			loop(i, 0, len){
				if(cellIndex == kCellSize){
					WriteCell(dest, cell);
					cellIndex = 0;
					memset(&cell, 0, kCellSize);
				}
				cell.as_bytes[cellIndex++] = mem[i];
			}
			if(cellIndex){
				WriteCell(dest, cell);
			}
			if(prefixWithSize){
				cell.as_uint = dest.size() - sizeIndex; // includes size prefix in size
				dest[sizeIndex] = cell;
			}
		}
		
		uint OpcodeCount() const{ return opcodes.size(); }
		
	private:
		uint32_t OpcodeSizeInCells() const{ return (opcodes.size() + sizeof(Cell) - 1) / sizeof(Cell); }
		
		typedef pair< uint32_t, uint32_t > Location;
		typedef pair<string, Location > PatchPoint;
		map< string, Location > labels;
		vector< PatchPoint > jumpLocations;

		uint32_t numParams;
		uint32_t varParamIndex;
		uint32_t numLocals;
		vector< int8_t > opcodes;
		vector< Cell > operands;
		vector< Cell > debug;
		uint debugOffsetsRecorded;
	};
	
	
	/// Class for creating assembly code from instructions given to be interpreted and executed by the compiler.
	class Assembler : public CodeWriter{
	public:
		Assembler() : lastSendCount(0), open(false) { }
		
		Assembler& StartMethod(uint32_t numParameters, uint32_t varParamIndex);
		
		Assembler& Number(double value);
		
		Assembler& Send(uint32_t numArgs, const char *msgStr);
		
		Assembler& Symbol(const char *name);

		Assembler& Get(int index);

		Assembler& Set(int index);

		Assembler& ObjectGet(const char *str);

		Assembler& ObjectSet(const char *str);

		Assembler& Return(int index);

		Assembler& Drop();

		Assembler& Object(uint32_t numArgs);
		
		Assembler& String(const char *str);

		Assembler& InitialiseArrayElement(const char *identifier, uint32_t element);
		
		Assembler& SetArrayElement(const char *str, uint32_t index);
		
		Assembler& GetArrayElement(const char *str, uint32_t index);

		Assembler& Code(const Cell *code, uint32_t codeSize);

		Assembler& Label(const char *str);
		
		Assembler& Jump(const char *str);
		
		Assembler& JumpFalse(const char *str);
		
		Assembler& EndMethod(uint32_t numLocals);
		
		void Get(Cell *dest, uint32_t size){
			Assert(!open);
			CodeWriter::Get(dest, size);
		}
		
	private:
		void Add(int8_t newCode, Cell cellOperand, int8_t smallOperand = 0);

		uint lastSendCount;
		bool open;
	};
	
	typedef void (*InstructionCallback)(ExecutionInfo &code, int indentLevel);
	
	void PrintInstruction(ExecutionInfo &code, int indentLevel);
	
	void Disassemble(InstructionCallback callback, const Cell *start, int indentLevel = 1);
	
	void Disassemble(const Cell *start);
}

#endif
